#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "ManejadorBits.h"
#include "codigos.h"
#include "vector_binario.h"

#define TAM_INI 10

/* *
 * 	COMPILAR CON:
 * 		gcc -std=c99 -Wall -pedantic -g -c "%f" -lm
 * 
 * 	CONSTRUIR CON:
 *		gcc -std=c99 -Wall -pedantic -g -o "%e" "%f" -lm
 * */

/* ********************************************************************
 *                		   FUNCIONES AUXILIARES					  	  *
 * ********************************************************************/

/* Calcula el logaritmo en base 2 de 'num' y devuelve el resultado
 * truncado a su parte entera. */
long log_piso ( long num) {
	
	double num_base10 = log (num);
	double num_dos = log (2);
	return ( num_base10 / num_dos );
}

/* Devuelve (2 ^ num). 'num' debe ser positivo. Devuelve 0 en caso de ser
 * negativo. */
long pot_base_dos ( long num) {
	
	if (num < 0) return 0;
	long rdo = 1;
	for ( long i = 0 ; i < num ; i++ ) {
		rdo *= 2;
	}
	return rdo;
}

/* Devuelve un vector binario con 'num' codificado en 'codigo' o NULL en
 * caso de error. */
vector_binario_t* codigo_nuevo (long num, void (*codigo) (vector_binario_t*, long) ) {
	vector_binario_t* vec = v_binario_crear (TAM_INI);
	if (!vec) return NULL;
	
	codigo (vec, num);
	return vec;
}

/* ********************************************************************
 *                		 PRIMITIVAS DE CODIGOS						  *
 * ********************************************************************/

/* **************************** UNARIO ********************************/
/* Agrega 'num' codificado en unario al final del vector binario
 * pasado por parámetro. */
void cod_unario (vector_binario_t* vec, long num) {
	if (!vec || num==0 ) return;
	for ( long i = 0 ; i < (num-1) ; i++ ) {
		v_binario_agregar (vec,1);
	}
	v_binario_agregar (vec,0);
}

/* Devuelve un vector binario con 'num' codificado en unario o NULL en
 * caso de error. */
vector_binario_t* cod_unario_nuevo (long num) {
	return codigo_nuevo (num, cod_unario);
}
 
/* Devuelve el numero que representa el vector del codigo unario.*/
long cod_unario_decimal(vector_binario_t* vec){
		return (v_binario_largo(vec));
	
}
/* Recibe un manejador y devuelve el valor en unario de lo leido*/


long cod_unario_leer (ManejadorBits_t* manejador){
	
	if (!manejador ||  ManejadorBits_es_modo_escritura (manejador) ) return -1;
		
	int actualizarShift = ManejadorBits_bits_ocupados (manejador);
	bool status = true;
	long numero = 0;
	
	if(	ManejadorBits_buffer_esta_vacio(manejador) )
		status = ManejadorBits_cargar_buffer(manejador);	
	if(!status) return -1;	
			
	char caracterLeido = ManejadorBits_ver_buffer(manejador);
	caracterLeido = caracterLeido << actualizarShift;

	char BitLeido = caracterLeido & 128;
	if( !BitLeido ) numero=0;
		
	while (BitLeido) {
		caracterLeido = caracterLeido << 1;
		numero+= 1;
		actualizarShift+= 1;
		if ( actualizarShift == 8 ){
			actualizarShift = 0;
			status = ManejadorBits_cargar_buffer(manejador);
			if(!status) return -1;
			caracterLeido = ManejadorBits_ver_buffer(manejador);
		}
		BitLeido = caracterLeido & 128;
	}
	
	numero ++;	//le sumo 1 por el 0
 	actualizarShift++; //le sumo 1 por el 0
	if(actualizarShift == 8 ) actualizarShift = 0;
	ManejadorBits_ocuparBits (manejador , actualizarShift);
	
	return numero;

}




/* **************************** BINARIO *******************************/
/* Agrega 'num' codificado en binario con la cantidad de bits especificada
 * al final del vector binario pasado por parámetro. */
void cod_binario (vector_binario_t* vec, long num, int bits) {
	if ( (num / (pot_base_dos(bits))) > 0 ) return;
	
	long actual = num;
	for ( int i = (bits-1) ; i >= 0 ; i-- ) {
		long divisor = pot_base_dos(i);
		if ( (actual / divisor) > 0 ) {
			actual -= divisor;
			v_binario_agregar (vec, 1);
		} else {
			v_binario_agregar (vec, 0);
		}
	}
}

/* Devuelve un vector binario con 'num' codificado en binario con la
 * cantidad de bits especificada o NULL en caso de error. */
vector_binario_t* cod_binario_nuevo (long num, int bits) {
	vector_binario_t* vec = v_binario_crear (TAM_INI);
	if (!vec) return NULL;
	cod_binario (vec, num, bits);
	return vec;
}

/* Devuelve el numero que representa el vector del codigo binario.*/
long cod_binario_decimal (vector_binario_t* vec){
	if( !vec || v_binario_largo(vec) == 0 ) return -1;
	int cantBits = v_binario_largo(vec);
	int bit,decimal = 0;
	
	for ( int a=0; a < cantBits ;a++){
		bit = v_binario_obtener(vec,a);
		decimal += bit*pow(2,cantBits-1-a);
	}
	return decimal;
}
/* leer en binario una cantidadBits determinada*/
long cod_binario_leer (ManejadorBits_t* manejador, long cantidad) {
	
	if(cantidad < 0)  return -1;
	if(cantidad == 0 ) return 0;
	if (!manejador ||  ManejadorBits_es_modo_escritura (manejador) ) return -1;
		
	int actualizarShift = ManejadorBits_bits_ocupados (manejador);
	bool status = true;
	long numero = 0;
	int bit;
	if(	ManejadorBits_buffer_esta_vacio(manejador) )
		status = ManejadorBits_cargar_buffer(manejador);	
	if(!status) return -1;	
			
	char caracterLeido = ManejadorBits_ver_buffer(manejador);
	caracterLeido = caracterLeido << actualizarShift;
	
	for ( int i=0; i < cantidad;i++){
		if ( actualizarShift == 8 ){
			actualizarShift = 0;			
			status = ManejadorBits_cargar_buffer(manejador);
			if(!status) return -1;
			caracterLeido = ManejadorBits_ver_buffer(manejador);
		}		
		char BitLeido = caracterLeido & 128;
		if( BitLeido ) bit = 1;
		else bit = 0;
		numero += bit * pow(2, (cantidad-1) - i);
		caracterLeido = caracterLeido << 1;
		actualizarShift++;
		
		
	}
	
	if(actualizarShift == 8 ) actualizarShift = 0;
	ManejadorBits_ocuparBits (manejador , actualizarShift);
	
	return numero;
}



/* **************************** GAMMA *********************************/
/* Agrega 'num' codificado en gamma al final del vector binario
 * pasado por parámetro. */
void cod_gamma (vector_binario_t* vec, long num) {
	if(!vec || num==0) return;
	long num2 = log_piso (num);
	cod_unario (vec, 1 + num2);
	cod_binario ( vec , num - (pot_base_dos(num2)) , num2 );
}

/* Devuelve un vector binario con 'num' codificado en gamma o NULL en
 * caso de error. */
vector_binario_t* cod_gamma_nuevo (long num) {
	return codigo_nuevo (num, cod_gamma);
}
/* Devuelve el numero que representa el vector del codigo gamma.*/
long cod_gamma_decimal(vector_binario_t* vec){

	if(!vec || v_binario_largo(vec) == 0) return -1;
	
	int bit,i=0,decimal=0,cantBits,cantidadUnos=0,unario;
	cantBits = v_binario_largo(vec);
	while( v_binario_obtener(vec,i) ==1 ){
		cantidadUnos += 1;
		i++;
	}
	unario = cantidadUnos+1;
	//tengo que leer cantidad total de bit menos i en binario entonces 
	for( int a=i;a<cantBits;a++){
		bit = v_binario_obtener( vec,a);
		decimal += bit * pow (2,cantBits-1-a);
	}
	return pow(2,unario-1) + decimal;

}

/* Devuelve el numero que representa el vector del codigo gamma.*/
long cod_gamma_leer (ManejadorBits_t* manejador ){
	
	if (!manejador ||  ManejadorBits_es_modo_escritura (manejador) ) return -1;
	
	long numUnario = cod_unario_leer(manejador);		
	if (numUnario == -1 ) return -1;	
	
	long numBinario = cod_binario_leer(manejador, numUnario-1);
	if (numBinario == -1 ) return -1;	
	
	return pow(2,numUnario-1) + numBinario;
}




/* **************************** DELTA *********************************/
/* Agrega 'num' codificado en delta al final del vector binario
 * pasado por parámetro. */
void cod_delta (vector_binario_t* vec, long num) {
	if(!vec || num==0) return;
	long num2 = log_piso (num);
	cod_gamma (vec, 1 + num2);
	cod_binario ( vec , num - (pot_base_dos(num2)) , num2 );
}

/* Devuelve un vector binario con 'num' codificado en delta o NULL en
 * caso de error. */
vector_binario_t* cod_delta_nuevo (long num) {
	return codigo_nuevo (num, cod_delta);
}

long cod_delta_decimal(vector_binario_t* vec){
	
	if(!vec || v_binario_largo(vec)==0) return -1;
	
	int largo,cantidadBit,bit,i=0,decimal=0,gamma,cantidadUnos=0,unario;
	largo = v_binario_largo(vec);
	//Leo Parte unario
	while( v_binario_obtener(vec,i) == 1){
		cantidadUnos += 1;
		i++;
	}
	unario = cantidadUnos+1;
	cantidadBit = 2*unario-1;
	//Leo parte binaria de Gamma
	for( int a=i; a < cantidadBit ; a++){
		bit = v_binario_obtener(vec,a);
		decimal += bit * pow (2,(cantidadBit-1-a));
	}
	gamma = pow(2,unario-1) + decimal;
	//Obtenido gamma leo la parte binaria de delta
	decimal = 0;
	for( int a = cantidadBit ; a < largo; a++){
		bit = v_binario_obtener(vec,a);
		decimal += bit * pow(2,(largo-1-a));
	}
	return pow(2,gamma-1) + decimal;	
	
}


long cod_delta_leer (ManejadorBits_t* manejador){
	
	if (!manejador ||  ManejadorBits_es_modo_escritura (manejador) ) return -1;
	
	long numGamma = cod_gamma_leer(manejador);		
	if (numGamma == -1 ) return -1;	
	
	long numBinario = cod_binario_leer(manejador, numGamma-1);
	
	if (numBinario == -1 ) return -1; 
	
	return pow(2,numGamma-1) + numBinario;	
	
	
}

